package com.j.lemon.learn.thread.demo;

import java.util.concurrent.*;

/**
 * @Author lijunjun
 * @Date 2019-03-25 16:46
 * @Description 前两个demo，都有着各自的问题。其实对于ConcurrentHashMap，需要解决重复计算的问题，当线程进来之后，我们需要知道当前参数的计算是否在进行中，
 * 如果在进行中，则等待计算完成，返回结果，如果没在进行中，直接进行计算，存缓存，返回结果。这里采用FutureTask。但是该demo仍然会有重复计算的问题，因为
 * if判断先检查再执行，不是原子的，有可能两个线程都从map中get到null
 */
public class Demo3<A, V> {
    private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(10000);
    //将future存入ConcurrentHashMap
    private final ConcurrentHashMap<A, Future<V>> map = new ConcurrentHashMap<>();
    private Computable<A, V> c;

    private Demo3(Computable<A, V> c) {
        this.c = c;
    }

    public V compute(A a) {
        //先判断是否在执行中
        Future<V> v = map.get(a);
        if (v == null) {
            Callable<V> callable = () -> c.compute(a);
            FutureTask<V> futureTask = new FutureTask<>(callable);
            map.put(a, futureTask);
            v = futureTask;
            futureTask.run();
        }

        V v1 = null;
        try {
            //该方法会阻塞，直到结果计算出来为止
            v1 = v.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return v1;
    }

    private static class TestThread<A, V> implements Runnable {
        private Demo3<A, V> demo1;
        private A k;

        TestThread(Demo3<A, V> demo1, A k) {
            this.demo1 = demo1;
            this.k = k;
        }

        @Override
        public void run() {
            demo1.compute(k);
            COUNT_DOWN_LATCH.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 16, 0, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(), r -> new Thread(r, "lala"), new ThreadPoolExecutor.DiscardPolicy());

        Demo3<String, Integer> demo1 = new Demo3<>(new ComputableImpl());
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            threadPoolExecutor.execute(new TestThread<>(demo1, "888"));
        }
        COUNT_DOWN_LATCH.await();
        long end = System.currentTimeMillis();

        System.out.println(end - start);
        threadPoolExecutor.shutdown();


    }
}
